AWS Step Functionsの変数を利用して、AWSサービスと連携してみた
はじめに
AWS Step Functionsの変数を利用して、AWSサービスと連携してみました。
以前、AWS Step Functions において変数が利用可能になるアップデートがありました。
AWS Step Functions ステートマシンに渡した値を変数として利用し、ステートマシン内で処理を行ったり、AWSサービスへのリクエスト結果を変数として後続の処理に渡すことができます。
また、JSONPath形式だけでなくJSONataが利用できるようになりましたので、JSONataを利用して変数の記述方法を解説します。
ステートマシンで処理している内容は以下の通りです。参考にした元記事は以下になります。
- EventBridgeからステートマシンに値を渡す
- コンタクトIDとConnectインスタンスIDが渡される
- Connect IDとConnectインスタンスIDをもとに、ConnectのGetContactAttributesでコンタクト属性を取得する
- コンタクト属性(キー名:recording、値:文字起こし内容)
- キー名がrecordingのコンタクト属性の値がnullではないか確認
- BedrockのClaudeを利用して、文字起こし内容を整形する
- 以下の内容をSNSトピックでメール送信する
- コンタクトID(問い合わせID)
- 問い合わせ時間
- 発信元電話番号
- 整形した文章
- 整形前の文章(コンタクト属性:recording)
ワークフローとしては、以下の図の通りです。各ステートごとに章立てで解説します。
各ステートにおいて、状態の入力(以降、ステートの入力)と状態の出力(以降、ステートの出力)は、以下の実行ログ(State View)から確認できます。
GetContactAttributesステート
まずは、最初のGetContactAttributesのステートについてです。
GetContactAttributesステートで設定、引数と出力、変数、の順で解説します。
設定
GetContactAttributesの設定は以下のとおりです。この設定については特にコメントすることはありません。
次に解説する[引数と出力]で、GetContactAttributesの実行で必要な値を設定します。
引数と出力
EventBridgeからステートマシンには、以下のようなイベントを渡されます。
{
"version": "0",
"id": "9788697f-b14a-61e5-30d0-ed1aec4e37af",
"detail-type": "Amazon Connect Contact Event",
"source": "aws.connect",
"account": "xxxxxxxx",
"time": "2024-11-27T01:04:22Z",
"region": "ap-northeast-1",
"resources": [
"arn:aws:connect:ap-northeast-1:xxxxxxxx:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c",
"arn:aws:connect:ap-northeast-1:xxxxxxxx:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c/contact/78c0a317-4e6f-40e3-b035-4bb8e879b15f"
],
"detail": {
"eventType": "CONTACT_DATA_UPDATED",
"contactId": "78c0a317-4e6f-40e3-b035-4bb8e879b15f",
"channel": "VOICE",
"instanceArn": "arn:aws:connect:ap-northeast-1:xxxxxxxx:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c",
"initiationMethod": "INBOUND",
"updatedProperties": ["UserDefinedAttributes"],
"initiationTimestamp": "2024-11-27T01:02:59.076Z",
"connectedToSystemTimestamp": "2024-11-27T01:02:59.555Z",
"tags": {
"aws:connect:instanceId": "3ff2093d-af96-43fd-b038-3c07cdd7609c",
"aws:connect:systemEndpoint": "+81xxxxxxx"
},
"segmentAttributes": {
"connect:Subtype": {
"valueString": "connect:Telephony"
}
}
}
}
実際に、ステートに渡されたイベント内容(ステートの入力)は、実行ログ(State View)からも確認できます。
GetContactAttributesのステートでは、コンタクト属性を取得するアクションになっております。
実行するには、引数としてインスタンス ID とコンタクト ID が必要です。
インスタンスIDとコンタクトIDは、渡される以下の2つの値を引数にする必要があります。
{
"input": {
~中略~
"detail": {
"contactId": "8ec3ee1b-4a8c-4c89-9473-217fd1dfe2d4",
"tags": {
"aws:connect:instanceId": "3ff2093d-af96-43fd-b038-3c07cdd7609c",
GetContactAttributesのステートで、インスタンスIDとコンタクトIDを引数として設定するには、以下のように記載します。
$states.input
は、ステートマシンへの入力を指します。
{
"InstanceId": "{% $states.input.detail.tags.\"aws:connect:instanceId\" %}",
"InitialContactId": "{% $states.input.detail.contactId %}"
}
InstanceIdのように、キー名に:
が入っている場合、バックスラッシュを使いエスケープさせる必要があります。
以下の場合エラーになります。
{
"InstanceId": "{% $states.input.detail.tags.aws:connect:instanceId %}",
"InitialContactId": "{% $states.input.detail.contactId %}"
}
GetContactAttributesの実行結果であるステートの出力は、[出力]ではなく[変数]で設定することで、値を後続のステートに渡します。
その変数の記載方法は次章で説明します。
ステートの入力を変数
GetContactAttributes ステートでは、ステートの入力のうち以下の項目を変数として設定します。
これらの変数は、SNS Publish ステートでメール送信する際にメール本文で利用されます。
- contactId:コンタクトID
- initiationTimestamp:問い合わせが開始された日時
- aws:connect:systemEndpoint:発信元電話番号
{
"input": {
~中略~
"detail": {
"contactId": "8ec3ee1b-4a8c-4c89-9473-217fd1dfe2d4",
"initiationTimestamp": "2024-12-05T07:44:44.614Z",
"tags": {
"aws:connect:systemEndpoint": "+81xxxxxxx",
~中略~
以下のように変数を設定します。
{
"InitialContactId": "{% $states.input.detail.contactId %}",
"Time": "{% $states.input.detail.initiationTimestamp %}",
"CallerPhoneNumber": "{% $states.input.detail.tags.\"aws:connect:systemEndpoint\" %}"
}
- 変数名とその値は以下の通りです。
- InitialContactId:contactId
- Time:initiationTimestamp
- CallerPhoneNumber:aws:connect:systemEndpoint
ステートの出力を変数
ステートの入力のうち3つを変数に設定しましたが、GetContactAttributesの実行結果であるステートの出力についても一部の値を変数として設定します。
GetContactAttributes を実行した際の実行結果(ステートの出力)として、コンタクト属性のキー名 recording
が取得されます。この値も変数として設定します。
まず、GetContactAttributesを実行した際に、ステートの出力は以下のような値になります。
(コンタクトIDに設定されたコンタクト属性なので、recording
ではない可能性もあります。)
{
"Attributes": {
"recording": "東京 都 港 区 西新橋 一 の 一 の 一 に 住ん で いる サトウ タロウ と 申し ます 先日 で も 問い合わせ し た ん です が 返事 が なく て 注文 し た 商品 が まだ 届か ない の で 確認 し たく て 電話 し まし た 注文 番号 は わかり ませ ん 住所 は 先程 言っ た 通り な ん です が マンション 名 を 言い 忘れ まし た 日比谷 ポート タワー に 十 六 回 です 実 は 先週 の 水曜 日 に 注文 し た はず な ん です が 確認 メール も 来 なく て 心配 です 普段 は 二 三 日 で 届く と 聞い て い た の で もし か し たら 注文 が 正しく 完了 し て い ない の か な と 思っ て 商品 は 確か 合計 で 一 万 二 千 八 百 円 分 注文 し た ん です が クレジット カード の 引き落とし も 確認 でき て い ませ ん"
}
}
ステートの出力のうち、recording
を変数に設定する場合、以下のように記述します。
"recording": "{% $states.result.Attributes.recording %}"
{
"recording": "{% $states.result.Attributes.recording %}",
"InitialContactId": "{% $states.input.detail.contactId %}",
"Time": "{% $states.input.detail.initiationTimestamp %}",
"CallerPhoneNumber": "{% $states.input.detail.tags.\"aws:connect:systemEndpoint\" %}"
}
$states.result
は、API が成功した場合の結果を指します。
ステートの入力を引数や変数として利用する場合は、"{% $states.input.xxx.xxx %}"
と記述します。
また、実行結果であるステートの出力を変数として利用する場合は、"{% $states.result.xxx.xxx %}"
と記述します。
$states.result
や$states.input
のほかにも以下を利用できます。
$states.errorOutput
:APIまたはサブワークフローが失敗した場合のエラー出力で使用$states.errorOutput
:キャッチフィールドのAssignまたはOutputで使用
詳細はドキュメントをご参照ください。
変数の設定は最終的に以下になります。
{
"recording": "{% $states.result.Attributes.recording %}",
"InitialContactId": "{% $states.input.detail.contactId %}",
"Time": "{% $states.input.detail.initiationTimestamp %}",
"CallerPhoneNumber": "{% $states.input.detail.tags.\"aws:connect:systemEndpoint\" %}"
}
Choiceステート
次にChoiceステートです。
ここでは、GetContactAttributes ステートで設定した変数 recording
が null でないかをチェックするルールを作成します。
ルールは以下のように記述します。$recording
を使用して表現可能です。
{% $recording != null %}
実行ログは以下のとおりです。特に確認すべき点はありません。
Bedrock InvokeModel
次に、BedrockのInvokeModelのステートです。
引数
InvokeModelを実行するため、引数は以下にしました。
文字起こし内容である recording
を整形するためのプロンプトを設定しています。
{
"ModelId": "arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0",
"Body": {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 1000,
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "{% '以下の音声文字起こしテキストから、ご要件、名前、住所の3つの情報を抽出し、以下のフォーマットで整理してください:\n\nご要件:[内容を自然な日本語で記述]\n\nお名前:[名前を記述]\n\nご住所:[住所を記述]\n\nなお、情報が不足している場合は「情報なし」と記載してください。\n余計な説明は不要です。上記フォーマットのみを返してください。\n\nテキスト:\n' & $recording %}"
}
]
}
]
}
}
変数である recording
を利用する場合、以下のように記述します。&
を挟んで利用します。
"text": "`{% '固定のプロンプト内容' & $recording %}`"
InvokeModel実行結果(ステートの出力)は、以下のようになります。
文章が整形されていることが確認できます。
{
"Body": {
"id": "msg_bdrk_0119c3DJogEEFczkqVYa7SZ2",
"type": "message",
"role": "assistant",
"model": "claude-3-5-sonnet-20240620",
"content": [
{
"type": "text",
"text": "ご要件:注文した商品がまだ届かないため、注文状況の確認を希望。先週水曜日に注文したはずだが、確認メールも来ておらず、クレジットカードの引き落としも確認できていない。\n\nお名前:サトウタロウ\n\nご住所:東京都港区西新橋1-1-1 日比谷ポートタワー16階"
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 521,
"output_tokens": 120
}
},
"ContentType": "application/json"
}
実行ログでも確認できます。
変数
ステートの出力のうち、Bedrockで整形した文章である以下であるキー名text
を次のステートであるSNS Publishでメール通知するため、text
を変数にします。
{
"Body": {
~中略~
"content": [
{
"type": "text",
"text": "ご要件:注文した商品がまだ届かないため、注文状況の確認を希望。先週水曜日に注文したはずだが、確認メールも来ておらず、クレジットカードの引き落としも確認できていない。\n\nお名前:サトウタロウ\n\nご住所:東京都港区西新橋1-1-1 日比谷ポートタワー16階"
}
],
以下で変数にできます。今回は変数名はbedrock_text
にしています。
{
"bedrock_text": "{% $states.result.Body.content[type=\"text\"].text %}"
}
実行結果を変数にするため、$states.result
にし、JSONataの場合、Body.content[type=\"text\"].text
と記載することで、キー名text
を指定できます。
設定
設定は以下のようになります。設定タブの[Bedrockモデルパラメータ]は、引数のBodyの設定値がそのまま表示されています。
SNS Publish
最後にSNS Publishのステートです。
引数
SNS Publish ステートでは、メール本文に GetContactAttributes ステートと InvokeModel ステートで設定した変数を利用します。
SNS Publish を実行するには、引数にメール本文(Message)と SNS トピック ARN が必要です。
メール送信先であるトピック ARN は固定値を使用しています。
引数としては、以下の変数を使用します。
- GetContactAttributesステートで設定した変数
$InitialContactId
:コンタクトID$Time
:問い合わせが開始された日時$CallerPhoneNumber
:発信元電話番号$recording
:コンタクト属性recording(文字起こし内容)
- InvokeModelステートで設定した変数
$bedrock_text
:recording(文字起こし内容)をBedrockで整形した文章
メール文章(Message)とSNSトピックARNを以下のように記載しました。
{
"Message": "{% 'お問い合わせ情報\n\n Contact ID: ' & $InitialContactId & '\n\n・日時:' & $Time & '\n\n・電話番号:' & $CallerPhoneNumber & '\n\n・お問い合わせ内容(整形済み文章)' & '\n\n' & $bedrock_text & '\n\n・お問い合わせ内容(未整形文章)' & '\n\n' & $recording %}",
"TopicArn": "arn:aws:sns:ap-northeast-1:xxxxxxxx:cm-hirai"
}
実際に送信されるメールは以下のような内容になります。
お問い合わせ情報
Contact ID: 78c0a317-4e6f-40e3-b035-4bb8e879b15f
・日時:2024-11-27T01:02:59.076Z
・電話番号:+81xxxxxxxx
・お問い合わせ内容(整形済み文章)
ご要件:先週水曜日に注文した商品が届かず、確認メールも来ていないため、注文状況を確認したい。
お名前:サトウ タロウ
ご住所:東京都港区西新橋1-1-1 日比谷ポートタワー 16階
・お問い合わせ内容(未整形文章)
東京 都 港 区 西新橋 一 の 一 の 一 に 住ん で いる サトウ タロウ と 申し ます 先日 で も 問い合わせ し た ん です が 返事 が なく て 注文 し た 商品 が まだ 届か ない の で 確認 し たく て 電話 し まし た 注文 番号 は わかり ませ ん 住所 は 先程 言っ た 通り な ん です が マンション 名 を 言い 忘れ まし た 日比谷 ポート タワー に 十 六 回 です 実 は 先週 の 水曜 日 に 注文 し た はず な ん です が 確認 メール も 来 なく て 心配 です 普段 は 二 三 日 で 届く と 聞い て い た の で もし か し たら 注文 が 正しく 完了 し て い ない の か な と 思っ て 商品 は 確か 合計 で 一 万 二 千 八 百 円 分 注文 し た ん です が クレジット カード の 引き落とし も 確認 でき て い ませ ん
設定
設定は以下のようになります。設定の [メッセージ] には、引数の Message の設定値が表示されています。
{% 'お問い合わせ情報
Contact ID: ' & $InitialContactId & '
・日時:' & $Time & '
・電話番号:' & $CallerPhoneNumber & '
・お問い合わせ内容(整形済み文章)' & '
' & $bedrock_text & '
・お問い合わせ内容(未整形文章)' & '
' & $recording %}
ステートマシン実行してみた
EventBridgeからステートマシンがトリガーされると、以下のメール内容が通知されました。
最後に
今回の記事では、AWS Step FunctionsでAWSサービスと連携時の変数の記述方法について解説しました。
変数を利用することで、ステートマシン内でのデータの受け渡しがより柔軟かつ効率的に行えることがわかりました。
また、JSONata を活用した変数の設定方法についても触れました。
実際に記述例を見ながら試してみることで、JSONata と変数について、どのように記述すればよいかがなんとなく理解できたのではないでしょうか。
JSONata を使いこなすことで、データの変換や抽出がより簡単に行えるようになりますので、ぜひ試してみてください。
参考